//Copyright (C) 2011 Ron Coleman. Contact: ronncoleman@gmail.com
//
//This library is free software; you can redistribute it and/or
//modify it under the terms of the GNU General Public License
//as published by the Free Software Foundation; either
//version 3 of the License, or (at your option) any later version.
//
//This library is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with this library; if not, write to the Free Software
//Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

package bj.actor
import scala.actors.Actor
import bj.hkeeping.Ok
import bj.hkeeping.NotOk
import bj.hkeeping.NotOk
import bj.hkeeping.Ok
import bj.hkeeping.Broke
import bj.card.Shoe
import bj.card.Card
import bj.card.Hand
import scala.collection.mutable.HashMap
import bj.hkeeping.NotOk
import scala.actors.OutputChannel
import bj.util.Logs

case class Request
case class Hit(pid: Int, h : Int) extends Request
case class Stay(pid: Int, h : Int) extends Request
case class DoubleDown(pid: Int, h : Int) extends Request
case class Split(pid: Int) extends Request
case class Surrender(pid: Int, h : Int) extends Request
case class Insurance(pid: Int) extends Request
case class Unknown(pid: Int) extends Request

case class NewHumanPlayer(pid: Int, seatID: Int, player: OutputChannel[Any])
case class Registered(seatid: Int)

case class OfferInsurance
case class InsuranceClosed
case class DealerBJ
case class MyTurn
case class PlaceBet
case class NewBet(pid: Int, amt: Double)
case class UpdateBet(player: OutputChannel[Any], pid: Int, amt: Double)
case class NewGame
case class MinMaxBets(min: Double, max: Double)

case class Up(card: Card)
case class Hole(card: Card)
case class DealerCard(card: Card)
case class ObserveH(card: Card, seat: Int, index: Int)
case class Observe(card: Card, seat: Int, index: Int)
case class ObserveSplit(seat: Int)
case class SplitCard(card: Card)
case class DealtCard(card: Card, index: Int)
case class WaitingPlayer(pid: Int, seat: Int, mailbox: OutputChannel[Any])
case class ResetHand
case class CloseInsurance
case class EndGame

case class Outcome
case class Win(gain: Double) extends Outcome
case class Lose(gain: Double) extends Outcome
case class Push(gain: Double) extends Outcome

/** This object implements the dealer static members */
object Dealer {
  /** Unique id counter */
  var id: Int = -1
}

/**
 * This class implements the dealer.
 * @param table The table I'm associated with.
 */
class Dealer(dealerName: String) extends Actor with Hand with Logs {
  /** Unique dealer id */
  Dealer.id += 1
  val did = Dealer.id

  // Dealer Name - setter and getter
  var nm = dealerName
  def name = nm
  def name_=(value: String): Unit = nm = value

  /** Mailboxes of players in an array - each slot corresponds to a seat   */
  var players: Array[OutputChannel[Any]] = new Array(Table.MAX_PLAYERS)
  
  /** Copies of hands of players at the table - each slot corresponds to a seat */
  var hands: Array[List[Hand]] = new Array(Table.MAX_PLAYERS)

  /** Players who have registered at the table but have not joined play yet */
  var waitingPlayers = List[WaitingPlayer]()

  /** Mailbox for the table */
  var table: OutputChannel[Any] = null

  /** Indicates whether the House has stopped this table */
  var stopTable : Boolean = false

  /** Hand-to-pid map */
  var handMap = HashMap[Object with Hand, Int]()

  /** Shoe from which cards are dealt */
  var shoe: Shoe = null

  /** Current player index */
  var playerIndex: Int = 0

  /** Current hand index */
  var handIndex: Int = 0
  
  /** Current player index */
  var curPlayer: OutputChannel[Any] = null

  /** Current hand in play */
  var curHand: Object with Hand = _

  /** My hole (faced down) card */
  var hole: Card = null
  
  /** A counter of how many remaining players need to place bets before play begins */
  var betsLeft: Int = 0
  
  /** Current table minimum bet */
  var currentMin = 0.0
  
  /** Current table maximum bet */
  var currentMax = 0.0
 
  /** Flag to indicated that dealer is accepting insurance bets */
  var insuranceOpen = false
  
  /** Flag to indicate that the table has changed */
  var tableChanged = true

  // Starts the dealer actor
  start

  /** Converts this instance to a string */
  override def toString: String = "dealer(" + did + ")"

  /** This method receives messages */
  def act {
    loop {
      react {
        
        // Receives players at the table
        case GameStart(players) =>
          debug(this + " received game start w. " + players.size + " player mailboxes")
          gameStart(players, sender)

        // Receives hit request from a player for a specific hand
        case Hit(pid, h) =>
          debug(this + " received HIT from player(" + pid + ")")
          hit(pid, h, sender)

        // Receives a stay request from a player for a specific hand
        case Stay(pid, h) =>
          debug(this + " received STAY from player(" + pid + ","+h+")")
          stay(pid, h, sender)

        // Receives double down request from a player for a specific hand
        case DoubleDown(pid, h) =>
          debug(this + " received DOUBLE DOWN from player(" + pid + ")")
          doubleDown(pid, h, sender)
        
        // Receives Split request from a player
        case Split(pid) =>
          debug(this + " received SPLIT from player (" + pid + ")")
          split(pid, sender)

        // Receives surrender request from a player for a specific hand
        case Surrender(pid, h) =>
          debug(this + " received SURRENDER from player (" + pid + ")")
          surrender(pid, h, sender)

        // Receives insurance request from a player
        case Insurance(pid) =>
          debug(this + " received INSURANCE request from player (" + pid + ")")
          buyInsurance(pid, sender)

        // accepts a new human player at the table
        case NewHumanPlayer(pid, seatid, player) =>
          debug(this + " received request to add new Human to seat (" + seatid + ")")
          newHumanPlayer(pid, seatid, player)

        // Receive a request (from a HumanPlayer) to collect and send back the table status (players' names and current cards)           
        case GetTableStatus =>
          sender ! TableStatus(getTableStatus())

        case GetTableInfo =>
          sender ! TableInfo(currentMin,currentMax,name)

        // Receives the current min and max bets from the Table
        case MinMaxBets(min,max) =>
          currentMin = min
          currentMax = max

        // Receives a bet from a player
        case NewBet(pid, b) =>
          debug(this + " received a bet amount of " + b + " from " + sender)
          table ! UpdateBet(sender, pid, b) // send the bet to the table to save it
          betsLeft -= 1
          if(betsLeft == 0) // if there are no more bets to collect, start the game
            init

        // Receives a message to close insurance bets
        case CloseInsurance => 
          insuranceOpen = false
          players.foreach(p => if(p!=null) p ! InsuranceClosed)
          startRound()

        // Receives a message to end the round and prepare a new one
        case EndGame => 
          processEndOfGame
            
        // Receives something completely from left field
        case dontKnow =>
          // Got something we REALLY didn't expect
          debug(this + " got " + dontKnow)
      }
    }
  }
  
  /**
   * Starts the game.
   * @param players Player mailboxes of this game
   * @param source Source actor of the message
   */
  def gameStart(playerList: List[OutputChannel[Any]], source: OutputChannel[Any]) {
    // save the sender's mailbox as the table
    table = source

    // add players' mailboxes to the players array
    var i = 0
    playerList.foreach(p => playerAdd(p))
    def playerAdd(p: OutputChannel[Any]): Unit = {
      players(i) = p
      i = i + 1
      p ! MinMaxBets(currentMin,currentMax)
      
      // drive the betting
      collectBets     
    }  
  }

  /**
   * This method adds a real human player to the waiting list
   * @param pid The players pid
   * @param seatid The seat at the table the player wants
   * @param player The player's mailbox
   */
  def newHumanPlayer(pid: Int, seatid: Int, player: OutputChannel[Any]) {
    // if the seat if free, register the player and add them to the waiting list
    if (players(seatid) == null) {
      player ! Registered(seatid)
      player ! MinMaxBets(currentMin, currentMax)
      waitingPlayers = waitingPlayers ::: List(WaitingPlayer(pid,seatid,player))
    } 
    else
      player ! NotOk
  }
  
  /** 
   * Initializes the game 
   */
  def init {
    if (shoe == null)
      shoe = new Shoe(did)

    // reset counters
    playerIndex = 0
    handIndex = 0
    
    // deal two cards to each player at the table
    players.foreach(deal)
    
    // Deal the up card to the dealer's hand
    hit(shoe.deal)

    // Deal the hole card but don't put it yet into dealer's hand
    hole = shoe.deal

    // send the dealer's up card to everyone
    players.foreach(p => if(p != null) p ! Up(cards(0)))
    
    // if the upcard is an ace, offer insurance and set a timer for insurance bets
    if (cards(0).ace) {
      insuranceOpen = true
      players.foreach(p => if(p!=null) p ! OfferInsurance)
      Timer ! TimeMe(3000,CloseInsurance)
    }
    // otherwise, start play
    else
      startRound()
  }

  /**
   * Drives the start of action
   */
  def startRound() {
    // check for dealer blackjack
    if ((cards(0).ace && hole.ten) || (cards(0).ten && hole.ace)) {
      dealerBJ
    } 
    // otherwise, play starts to the dealer's left
    else {
      // find the first player
      curPlayer = players.filter(p => p!=null)(0)
      playerIndex = players.indexOf(curPlayer)

      // tell him to act
      players(playerIndex) ! MyTurn
    }
    
  }

  /**
   * This method prompts each player to select a new bet
   */
  def collectBets {
    betsLeft = playersAtTable
    players.foreach(p => if(p!=null) p ! PlaceBet)
  }

  /**
   * Deals initial cards to a player
   * @param mailbox The player's mailbox
   */
  def deal(player: OutputChannel[Any]) {
    // if there is a player at this seat
    if (player != null) {
      // deal two cards from the shoe
      val card1 = shoe.deal
      val card2 = shoe.deal

      debug(this + " dealing " + card1 + " to " + player)
      debug(this + " dealing " + card2 + " to " + player)

      // create a new hand object and hit the two cards to hit
      val hand = new Object with Hand
      hand.hit(card1)
      hand.hit(card2)

      // the dealer keeps a copy of the hand
      hands(playerIndex) = List(hand)

      // send the cards to the player
      deal(player, card1)
      deal(player, card2)   
    }
    // increment counter
    playerIndex += 1
  }

  /**
   * Sends the destination player a card.
   * @param target Mailbox of the destination player.
   * @param card Card to send
   */
  def deal(player: OutputChannel[Any], card: Card) {
    // Send the card to the player
    player ! DealtCard(card,handIndex)
    
    // notify all other players of this card    
    broadcast(card, player)
  }
  
  /**
   * This method broadcasts a card for all players other than the current player to observe
   * @param card The card to broadcast
   * @param player The mailbox of the current player who is already receiving the card
   */
  def broadcast(card : Card, player: OutputChannel[Any]) : Unit = {
    // send to all players at the table
    for (i <- (0 until players.size)) {
      if (players(i) != null && players(i) != player) {
        if(hands(playerIndex)(handIndex).doubled)
          players(i) ! ObserveH(card,playerIndex, handIndex) // ObserveH is horizontal (for GUI)
        else
          players(i) ! Observe(card, playerIndex, handIndex)
      }
    }
    // send to all players in waiting list so they can see action at the table
    for (i <- (0 until waitingPlayers.size)) {
      if(hands(playerIndex)(handIndex).doubled)
        waitingPlayers(i).mailbox ! ObserveH(card,playerIndex,handIndex)
      else
        waitingPlayers(i).mailbox ! Observe(card,playerIndex,handIndex)
    }
  }

  /**
   * This method handles the case of a dealer blackjack
   */
  def dealerBJ {
    debug(this + "Sorry, dealer has blackjack.")

    // close the dealers hand and do the payouts
    close
    payout

    // set a timer to trigger end of game activity
    Timer ! TimeMe(3000,EndGame)
  }

  /**
   * This processes a hit request for a specific hand
   * @param pid The players pid
   * @param index The index of the hand (for splitting)
   * @param player Source actor of the message.
   */
  def hit(pid: Int, index : Int, player: OutputChannel[Any]) {
    // if this request is for the wrong player or hand, do nothing
    if (!valid(player) || handIndex != index) {
      player ! NotOk
    }
    
    else {
      // hit the next card and deal it to the player
      val card = shoe.deal
      deal(player, card)

      // add it to the dealer's copy of the hand
      hands(playerIndex)(index).hit(card)

      // if it broke the player, let him know
      if (hands(playerIndex)(index).broke) {
        player ! Broke

      // if this is the first hand of a split, we move to the second
      if(handIndex == 0 && hands(playerIndex).size > 1) {
        handIndex = 1
      }      
      // Otherwise the next turn belongs to the next player's first hand
      else
        handIndex = 0

        // move on to the next hand
        moveNext()
      }
    }
  }

  /**
   * This method processes a player request for insurance
   * @param pid The requesting players pid
   * @param player The requesting player's mailbox
   */
  def buyInsurance(pid: Int, player: OutputChannel[Any]) {
    // as long as insurance did not close
    if (insuranceOpen) {
      // find the player's hand and set the insurance flag
      hands(players.indexOf(player))(0).insurance = true
      player ! Ok
    }
    else
      player ! CloseInsurance
  }

  /**
   * This method process a double down request for a player
   * @param pid The requesting players pid
   * @param index The hand which should be hit (for split hands)
   * @param player Source actor of the message.
   */
  def doubleDown(pid: Int, index : Int, player: OutputChannel[Any]) {
    // if this request is for the wrong player or hand, do nothing
    if (!valid(player) || handIndex != index)
      player ! NotOk

    else {
      // hit the next card and deal it to the player
      val card = shoe.deal
      deal(player, card)

      // add the card to the dealer's copy of the hand and set the doubledown flag to true
      hands(playerIndex)(index).doubled = true
      hands(playerIndex)(index).hit(card)

      // if this card broke the player, let him know
      if (hands(playerIndex)(index).broke) {
        player ! Broke
      }
      
      // if this is the first hand of a split, we move to the second
      if(handIndex == 0 && hands(playerIndex).size > 1) {
        handIndex = 1
      }
      // Otherwise the next turn belongs to the next player's first hand 
      else
        handIndex = 0

      // move on to the next hand
      moveNext()
    }
  }

  /**
   * This method handles a player split
   * @param pid The players pid
   * @param sender The mailbox of the sender/player
   */
  def split(pid: Int, sender: OutputChannel[Any]) {
    // tell each player and waiting player that a split has happened (for GUI purposes)
    players.foreach(p=> if(p!=null) p ! ObserveSplit(playerIndex))
    waitingPlayers.foreach(w => w.mailbox ! ObserveSplit(playerIndex))
    
    // create new instances of hand objects
    val hand1 = new Object with Hand
    val hand2 = new Object with Hand
    // deal the next card
    val card1 = shoe.deal

    debug(this + " dealing " + card1)

    // set the flags for these hands to indicate a split occurred
    hand1.didSplit = true
    hand2.didSplit = true  
 
    // split the original hand apart and recombine as appropriate
    hand1.cards = List(hands(playerIndex)(0).cards(0), card1)
    hand2.cards = List(hands(playerIndex)(0).cards(1))

    // replace the original List(hand) of size 1 with the new List(hand) of size 2
    hands(playerIndex) = List(hand1, hand2)

    // send the new card to the player and tell everyone else what the card was
    sender ! SplitCard(card1)
    broadcast(card1,sender)

  }

  /**
   * This method processes a stay request for a player
   * @param pid Requesting player's pid
   * @param index The hand for which stay is requested (for split hands)
   * @param source Source actor of the message.
   */
  def stay(pid: Int, index : Int, source: OutputChannel[Any]) {
    // if this request is for the wrong player or hand, do nothing
    if (!valid(source) || index != handIndex) {
      source ! NotOk
    }
    
    else {
      source ! Ok
      
      // if this is the first hand of a split, we move to the second
      if(handIndex == 0 && hands(playerIndex).size > 1) {
        handIndex = 1
      }
      //Otherwise the next turn belongs to the next player's first hand
      else
        handIndex = 0

      // move on to the next hand
      moveNext()
    }
  }
  
  /**
   * This method processes a surrender request for a player
   * @param pid Requesting player's pid
   * @param index The hand for which surrender is requested (for split hands)
   * @param source Source actor of the message.
   */
  def surrender(pid: Int, index : Int, source: OutputChannel[Any]) {
    // if this request is for the wrong player or hand, do nothing
    if (!valid(source) || handIndex != index) {
      source ! NotOk
    }
    
    else {
      // set the surrender flag to true for the dealer's copy of the hand
      hands(playerIndex)(index).surrendered = true
      source ! Ok
      
      // If this is the first hand of a split, we move to the second
      if(handIndex == 0 && hands(playerIndex).size > 1) {
        handIndex = 1
      }
      // Otherwise the next turn belongs to the next player's first hand
      else {
        handIndex = 0
      }
      
      // move on to the next hand
      moveNext()
    }
  }

  /**
   * Moves to the next player at the table.
   */
  def moveNext() {
    // When we called this method, we set the handIndex already
    if (handIndex == 0) {
      // Find the next player and set playerIndex
      do {
        playerIndex += 1
      } while (playerIndex < players.size && players(playerIndex) == null)

      // If there is another player to play, tell him to act
      if (playerIndex < players.size && players(playerIndex) != null) {
        players(playerIndex) ! MyTurn
      }
      // If no more players, end the gane
      else {
        // Close the game with dealer play and payouts
        close
        payout

        // set a timer to trigger end of game activity
        Timer ! TimeMe(3000,EndGame)
      }
    } 
    // Hand index was 1 and we play the second hand of the split
    else {
      //deal the second card and send it to the player
      val nextCard = shoe.deal
      deal(players(playerIndex),nextCard)
      
      // append the card to the dealer copy of the second hand
      hands(playerIndex)(handIndex).cards = hands(playerIndex)(handIndex).cards ::: List(nextCard)
      
      //then notify that it is time to act
      players(playerIndex) ! MyTurn
    }
  }

  /** 
   * Returns count of players who haven't broken 
   */
  def playersLeft = hands.count(h => playerAlive(h))

  /**
   * This method analyzes a player to see if they can still win the hand
   * @param h The hand to be analyzed
   */
  def playerAlive(h: List[Hand]): Boolean = {
    // if the hand is null, there is no player
    if (h == null)
      return false
    // if the hand did not split (size 1), player is alive if he did not break and did not surrender
    if (h.size == 1)
      return !h(0).broke && !h(0).surrendered
    // otherwise, the hand is of size two, and the player is alive as long as one hand did not break or surrender
    // logic checks out with DeMorgan's law
    return (!h(0).broke && !h(0).surrendered) && (!h(1).broke && !h(1).surrendered)

  }
  
  /** 
   * Closes out the game with dealer action 
   */
  def close {
    // Hit dealer's hand with the hole card
    hit(hole)

    debug(this + " closing card1 = " + cards(0) + " card2 = " + cards(1))

    // if there are any players who can beat the dealer
    if (playersLeft != 0) {
      // keep hitting until dealer is between 17 and 21
      while (value < 17 && !blackjack) {
        val card: Card = shoe.deal
        hit(card)

        debug(this + " hitting card = " + card + " value = " + value)
      }
      if (value > 21)
        debug(this + " BROKE!!")
    }

    // Broadcast observations on all cards dealer's holding
    // except the up-card at index 0 -- players have already
    // seen this card
    for (i <- (0 until players.size)) {
      if (players(i) != null) {
        players(i) ! Hole(cards(1))
        for (j <- (2 until cards.size)) {
          players(i) ! DealerCard(cards(j))
        }
      }
    }
  }
  
  /** 
   * Sends pay-out info to the table 
   */
  def payout {
    val pays = HashMap[Int, Outcome]()

    var payIndex = 0

    hands.foreach(pay)
    def pay(h: List[Hand]) {
      if (h != null) {
        // get pid of the player owning this hand
        val pid = players(payIndex).receiver.asInstanceOf[Player].pid
        
        // all gains start at 0
        var gain = 0.0

        // if the hand was not split, the gain is just for the one hand
        if (h.size == 1) {
          gain = calculateGain(h(0))
        } 
        // if the hand was split, the gain is the sum of the two hands
        else if (h.size == 2) {
          gain = calculateGain(h(0)) + calculateGain(h(1))
        }
        
        // if gain is positive, the player won
        if (gain > 0) {
          pays(pid) = Win(gain)
        } 
        // if was negative, the player lost
        else if (gain < 0) {
          pays(pid) = Lose(gain)
        }
        // otherwise it was a push
        else {
          pays(pid) = Push(0.0)
        }

      }

      // increment the index
      payIndex += 1
    }
    // send the hashmap to the table
    table ! GameOver(pays)
  }

  /**
   * This method calculates the gain ratio for a hand against the dealer
   * @param hand The hand to be analyzed
   */
  def calculateGain(hand: Hand): Double = {
    // the factor always starts at 1, the gain always at 0
    var factor = 1.0
    var gain = 0.0

    // the hand included a double down, the factor is multiplied by 2
    if (hand.doubled) {
      factor = factor * 2
    }

    // if the player broke, the gain becomes negative
    if (hand.broke)
      gain = -1.0 * factor

    // if the dealer did not break and beat the value of this hand, the gain is negative
    else if (!this.broke && hand.value < this.value)
      gain = -1.0 * factor

    // if the dealer tied this hand the gain should be 0
    else if (this.value == hand.value) {
      if(hand.blackjack && !this.blackjack) { 
        // except if the player had blackjack and the dealer got 21 in 3 cards or more
        // this pays 3:2
        gain = 1.5
      }
      else {
        gain = 0.0
      }
    }

    // if the player gets blackjack, he is paid 3:2
    else if (hand.blackjack)
      gain = 1.5

    // if the player beats the dealer, gain is positive
    else if (hand.value > this.value)
      gain = factor

    // if the player did not break but the dealer did, gain is positive
    else if (!hand.broke && this.broke)
      gain = factor

    // if the player surrendered this hand, gain is automatically -0.5 regardless of previous computations   
    if (hand.surrendered) {
      gain = -0.5
    }

    // if player bought insurance
    if (hand.insurance && this.blackjack) {
      // and the dealer had blackjack, the gain is incremented by 1
      gain = gain + 1.0
    } 
    else if (hand.insurance) {
      // otherwise he loses the insurance bet and gain is decreased by 0.5
      gain = gain - 0.5
    }

    // return value
    gain
  }


  /**
   * Validates a mailbox
   * @param mailbox The player's mailbox
   */
  def valid(player: OutputChannel[Any]): Boolean = {
    if (player == null || playerIndex >= Table.MAX_PLAYERS)
      return false

    players.find(p => p == player) match {
      case None =>
        return false

      case Some(player) =>
        if (player != players(playerIndex))
          return false

        return !hands(playerIndex)(handIndex).broke
    }
  }
  
  /**
   * This method returns the number of players sitting and active at the table
   */
  def playersAtTable: Int = players.count(p => p!=null)

  /**
   * This method will clean up the current round a prepare for the next
   */
  def processEndOfGame  {
    // if the table is not scheduled to be stopped
    if(!stopTable) {
      // clear the dealer's cards
      cards = List()
      // tell each player to reset wipe his hand
      players.foreach(p => if(p!=null) p ! ResetHand)
      // create a new hands array to store copies of the player's hands
      hands = new Array[List[Hand]](Table.MAX_PLAYERS)
      // reset counters
      playerIndex = 0
      handIndex = 0
      
      // add waiting players to the table
      processWaitingPlayers
      // get all active player's bets for the next hand
      collectBets
    }
    else
      players.foreach(p => if(p!=null) p ! GameOver)
  }

  /**
   * This methods moves waiting players to the active players array
   */  
  def processWaitingPlayers() {
    // add each waiting player to the table
    waitingPlayers.foreach(addToTable)
    
    // clear the List
    waitingPlayers = List()
    
    def addToTable(w: WaitingPlayer) {
      if(players(w.seat) == null) {
        players(w.seat) = w.mailbox
        tableChanged = true
      } 
      else
        w.mailbox ! NotOk
    }

    // if a new player joined, update everyone's table status
    if (tableChanged)
      players.foreach(p=> if(p!=null) p !  TableStatus(getTableStatus()))
    tableChanged = false  
  }

  // Gets the players' names and current cards for each player at the table
  def getTableStatus(): Array[(String, List[Card])] = {
    var playersArray: Array[(String, List[Card])] = new Array(Table.MAX_PLAYERS)
    var playerCards: List[Card] = null
    var i = 0;
    var playerName: String = ""
    players.foreach(p => getPlayerData(p))
    def getPlayerData(p: OutputChannel[Any]): Unit = {
      if (p != null) {
        debug(p + "")
        playerName = p.receiver.asInstanceOf[Player].name
        handMap.find(h => h._2 == p.receiver.asInstanceOf[Player].pid) match {
          case None =>
            //this shouldn't happen
            playerCards = List()
          case Some(playerHand) => playerCards = playerHand._1.cards
        }
        /*if (playerCards == null) {
              playerCards = List()
            }*/
        val newPlayer: (String, List[Card]) = (playerName, playerCards)
        debug(i + " " + playerName + " " + playerCards)
        playersArray(i) = newPlayer

      }
      i += 1

    }
    playersArray
  } 
}